#include "../include/tokyo.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <stdlib.h>


gboolean net_main ()
{
	/* Avvio del socket */
	control_socket = net_init_socket (NET_PORT);

	if (control_socket == -1)
		return FALSE;

	net_loop ();

	return TRUE;
}

gint net_init_socket (gint port)
{
	struct sockaddr_in sa;
	gint x = 1;
	gint fd;

	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		tlog (P_ERROR, "net_init_socket", _("Errore nella creazione del socket."));
		return -1;
	}

	if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *) &x, sizeof (x)) < 0)
	{
		tlog (P_ERROR, "net_init_socket", _("SO_REUSEADDR non applicabile."));
		close (fd);
		return -1;
	}

	memset (&sa, '\0', sizeof (sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons (port);

	if (bind (fd, (struct sockaddr *) &sa, sizeof (sa)) == -1)
	{
		tlog (P_ERROR, "net_init_socket", _("socket bind."));
		close( fd );
		return -1;
	}

	if (listen (fd, 50) < 0)
	{
		tlog (P_ERROR, "net_init_socket", _("socket listen."));
		close( fd );
		return -1;
	}

	return fd;
}

gboolean net_check_bad_desc (gint desc)
{
	if (FD_ISSET (desc, &exc_set))
	{
		FD_CLR (desc, &in_set);
		FD_CLR (desc, &out_set);
		tlog (P_ERROR, "net_check_bad_desc", _("Remove bad descriptor %d."), desc);
		return TRUE;
	}

	return FALSE;
}

void net_new_descriptor (gint new_desc)
{
	S_DESCRIPTOR *dnew;
	gchar log_buf[MSL];
	struct sockaddr_in sock;
	gint desc;
	guint size;

	size = sizeof (sock);

	if (net_check_bad_desc (new_desc))
		return;

	if ((desc = accept (new_desc, (struct sockaddr *) &sock, &size)) < 0)
	{
		tlog (P_ERROR, "net_new_descriptor", _("Errore: accept."));
		return;
	}

	if (net_check_bad_desc (new_desc))
		return;

#if !defined(FNDELAY)
#define FNDELAY O_NDELAY
#endif

	if (fcntl (desc, F_SETFL, FNDELAY) == -1)
	{
		tlog (P_ERROR, "net_new_descriptor", _("Errore: fcntl FNDELAY not set."));
		return;
	}

	if (net_check_bad_desc (new_desc))
		return;

	g_strlcpy (log_buf, inet_ntoa (sock.sin_addr), MSL);

	dnew = g_new (S_DESCRIPTOR, 1);
	dnew->descriptor = desc;
	dnew->port = ntohs (sock.sin_port);
	dnew->inbuf[0] = '\0';
	dnew->host = g_strdup (log_buf);
	dnew->fcommand = FALSE;
	dnew->outtop = 0;
	dnew->outsize = 2000;

	/* TESTDEBUG */
	dnew->character = g_new (S_CHAR, 1);
	char_init (dnew->character);
	dnew->character->name = g_strdup ("Char Prova");
	dnew->character->desc = dnew;
	/******/

	dnew->connect_state = NET_CSTATE_NEW_DESC;

	dnew->outbuf = g_new (gchar, dnew->outsize);

	descriptors = g_list_append (descriptors, dnew);

	tlog (P_NORMAL, "net_new_descriptor", _("Nuovo descrittore connesso: %s@%d"), dnew->host, dnew->port);

	return;
}

void net_accept_new (gint ctrl)
{
	static struct timeval null_time;
	GList *list = NULL, *list_next = NULL;
	S_DESCRIPTOR *d;

	FD_ZERO( &in_set );
	FD_ZERO( &out_set );
	FD_ZERO( &exc_set );
	FD_SET( ctrl, &in_set );
	maxdesc = ctrl;
	newdesc = 0;

	for (list = g_list_first(descriptors); list; list = list_next )
	{
		list_next = g_list_next(list);

		d = (S_DESCRIPTOR*) (list->data);

		maxdesc = MAX (maxdesc, d->descriptor);
		FD_SET( d->descriptor, &in_set );
		FD_SET( d->descriptor, &out_set );
		FD_SET( d->descriptor, &exc_set );
	}

	if (select (maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) < 0)
	{
		tlog (P_NORMAL, "net_accept_new", _("select pool."));
		return; // EXIT
	}

	if (FD_ISSET (ctrl, &exc_set))
	{
		tlog (P_NORMAL, "net_accept_new", _("Eccezione (%d)."), ctrl);
		FD_CLR (ctrl, &in_set);
		FD_CLR (ctrl, &out_set);
	}
	else if (FD_ISSET (ctrl, &in_set))
	{
		newdesc = ctrl;
		net_new_descriptor (newdesc);
	}
}



void net_close_socket (S_DESCRIPTOR *dclose, gboolean force)
{
	descriptors = g_list_remove (descriptors, dclose);

	if (dclose->descriptor == maxdesc)
		--maxdesc;

	tlog (P_NORMAL, "net_close_socket", _("Connessione chiusa con %s@%d"), dclose->host, dclose->port);

	if (dclose->host)
		g_free (dclose->host);
	if (dclose->outbuf)
		g_free (dclose->outbuf);

	g_free (dclose);

	return;
}


gboolean net_read_from_descriptor (S_DESCRIPTOR *d)
{
	unsigned int iStart;
	int iErr;

	/*
	* Check for overflow. 
	*/
	iStart = strlen (d->inbuf);
	if (iStart >= sizeof (d->inbuf) - 10)
	{
		tlog (P_NORMAL, "net_read_from_descriptor", _("Overflow!! %d"), d->descriptor);
		return FALSE;
	}

	for( ;; )
	{
		int nRead;

		nRead = recv (d->descriptor, d->inbuf + iStart, sizeof (d->inbuf) - 10 - iStart, 0);

		iErr = errno;

		if (nRead > 0)
		{
			iStart += nRead;
			if (d->inbuf[iStart - 1] == '\n' || d->inbuf[iStart - 1] == '\r')
				break;
		}
		else if (nRead == 0)
		{
			tlog (P_NORMAL, "net_read_from_descriptor", _("EOF on read %d"), d->descriptor);
			return FALSE;
		}
		else if (iErr == EWOULDBLOCK)
			break;
		else
		{
			tlog (P_NORMAL, "net_read_from_descriptor", _("Error on read %d"), d->descriptor);
			return FALSE;
		}
	}

	d->inbuf[iStart] = '\0';
	return TRUE;
}

gboolean net_write_to_descriptor (S_DESCRIPTOR *d, gchar *txt, gint length)
{
	gint iStart = 0;
	gint nWrite = 0;
	gint nBlock;
	gint iErr;

	if (length <= 0)
		length = strlen (txt);

	for (iStart = 0; iStart < length; iStart += nWrite)
	{
		nBlock = MIN (length - iStart, 4096);
		nWrite = send (d->descriptor, txt + iStart, nBlock, 0);

		if (nWrite == -1)
		{
			iErr = errno;
			if (iErr == EWOULDBLOCK)
			{
				nWrite = 0;
				continue;
			}
			else
			{
				tlog (P_ERROR, "net_write_to_descriptor", _("Errore in send (%d)"), iErr);
				return FALSE;
			}
		}
	}

	return TRUE;
}


/*
 * Low level output function.
 */
gboolean net_flush_buffer (S_DESCRIPTOR * d, gboolean fPrompt)
{
	char buf[MIL];

   /*
    * If buffer has more than 4K inside, spit out .5K at a time   -Thoric
    */
	if (d->outtop > 4096)
	{
		memcpy (buf, d->outbuf, 512);
		d->outtop -= 512;
		memmove (d->outbuf, d->outbuf + 512, d->outtop);

		if (!net_write_to_descriptor (d, buf, 512))
		{
			d->outtop = 0;
			return FALSE;
		}

		return TRUE;
	}

	if (fPrompt)
	{
		char_send_prompt (d->character);
	}

	g_print (d->outbuf);

   /*
    * Short-circuit if nothing to write.
    */
	if( d->outtop == 0 )
		return TRUE;

   /*
    * OS-dependent output.
    */
	if (!net_write_to_descriptor (d, d->outbuf, d->outtop))
	{
		d->outtop = 0;
		return FALSE;
	}
	else
	{
		d->outtop = 0;
		return TRUE;
	}
}

void net_write_to_buffer (S_DESCRIPTOR * d, const gchar *txt, guint length)
{
	if (!d)
	{
		tlog (P_ERROR, "net_write_to_buffer", _("NULL descriptor"));
		return;
	}

   /*
    * Normally a bug... but can happen if loadup is used.
    */
	if (!d->outbuf)
		return;

   /*
    * Find length in case caller didn't.
    */
	if (length <= 0)
		length = strlen (txt);

   /*
    * Initial \r\n if needed.
    */
	if (d->outtop == 0 && !d->fcommand)
	{
		d->outbuf[0] = '\r';
		d->outbuf[1] = '\n';
		d->outtop = 2;
	}

   /*
    * Expand the buffer as needed.
    */
	while (d->outtop + length >= d->outsize)
	{
		if (d->outsize > 32000)
		{
         /*
          * empty buffer 
          */
			d->outtop = 0;
         /*
          * Bugfix by Samson - moved bug() call up 
          */
			tlog (P_ERROR, "net_write_to_buffer", _("Buffer overflow. Chiusa connessione con %d."), d->descriptor);
			net_close_socket (d, TRUE);
			return;
		}
		d->outsize *= 2;
		RECREATE( d->outbuf, char, d->outsize );
	}

   /*
    * Copy.
    */
	g_strlcpy (d->outbuf + d->outtop, txt, d->outsize-d->outtop);
	d->outtop += length;
	d->outbuf[d->outtop] = '\0';

	return;
}

void net_state_loop (S_DESCRIPTOR* d, gchar *argument)
{
	S_CHAR* ch;

	ch = d->character;

	while (g_ascii_isspace (*argument))
		argument++;

	switch (d->connect_state)
	{
		default:
			tlog (P_ERROR, "net_state_loop", _("Stato della connessione invalido d:%d state:%d"), d, d->connect_state);
			net_close_socket( d, TRUE );
			return;
		break;

		case NET_CSTATE_NEW_DESC:
			char_d_send (d, NET_CTYPE_NORMAL, SEND_STRING, "Benvenuto!", SEND_STOP);
			char_d_send (d, NET_CTYPE_NORMAL, SEND_STRING, "Inserisci il tuo nome:", SEND_STOP);
		break;

	}
}

void net_loop (void)
{
	struct timeval last_time;
	gchar cmdline[MIL];
	GList *list = NULL, *list_next = NULL;
	S_DESCRIPTOR *d;

	gettimeofday (&last_time, NULL);

	while (1)
	{
		net_accept_new (control_socket);

		/* IN */
		for (list = g_list_first(descriptors); list; list = list_next )
		{
			list_next = g_list_next(list);

			d = (S_DESCRIPTOR*) (list->data);

			if (FD_ISSET (d->descriptor, &exc_set))
			{
				FD_CLR (d->descriptor, &in_set);
				FD_CLR (d->descriptor, &out_set);
				d->outtop = 0;
				net_close_socket (d, TRUE);
				continue;
			}
			else
			{
				d->fcommand = FALSE;

				if (FD_ISSET (d->descriptor, &in_set))
				{

					if (!net_read_from_descriptor (d))
					{
						FD_CLR (d->descriptor, &out_set);
						d->outtop = 0;
						net_close_socket (d, FALSE);
						continue;
					}

					if (d->inbuf[0] != '\0')
					{
						d->fcommand = TRUE;
						g_strlcpy (cmdline, d->inbuf, MIL);
						d->inbuf[0] = '\0';

						if (d->connect_state == NET_CSTATE_PLAY)
						{
							char_send (d->character, NET_CTYPE_NORMAL, "Sei in gioco\n\r");
						}
						else
						{
							net_state_loop (d, cmdline);
						}
					}
				}
			}
		}


		/* Ciclo di Update */


		/* OUTPUT */
		for (list = g_list_first(descriptors); list; list = list_next )
		{
			list_next = g_list_next(list);

			d = (S_DESCRIPTOR*) (list->data);

			if ((d->fcommand || d->outtop > 0) && FD_ISSET (d->descriptor, &out_set))
			{
				if (!net_flush_buffer (d, TRUE))
				{
					d->outtop = 0;
					net_close_socket (d, FALSE);
				}
			}
		}

		/* TIME */
		{
			struct timeval now_time;
			long secDelta;
			long usecDelta;

			gettimeofday (&now_time, NULL);
			usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec) + 1000000 / 4;
			secDelta = ((int) last_time.tv_sec) - ((int) now_time.tv_sec);

			while (usecDelta < 0)
			{
				usecDelta += 1000000;
				secDelta -= 1;
			}

			while (usecDelta >= 1000000)
			{
				usecDelta -= 1000000;
				secDelta += 1;
			}

			if (secDelta > 0 || (secDelta == 0 && usecDelta > 0))
			{
				struct timeval stall_time;

				stall_time.tv_usec = usecDelta;
				stall_time.tv_sec = secDelta;

				if (select (0, NULL, NULL, NULL, &stall_time) < 0 && errno != EINTR)
				{
					tlog (P_NORMAL, "net_loop", _("Stall!."));
					return;
				}
			}
		}
	}

	return;
}
